﻿using System;
using System.Diagnostics;
using System.Globalization;

namespace HSS
{
	internal static class Guard
	{
		sealed class ValidatedNotNullAttribute : Attribute { }

		/// <summary>
		/// Throws <see cref="NullReferenceException"/> if the given value is null.
		/// </summary>
		/// <exception cref="NullReferenceException"> if tested value is null.</exception>
		/// <typeparam name="T">The Type of the object being checked.</typeparam>
		/// <param name="value">The object itself.</param>
		/// <param name="message">A <see cref="String"/> that describes the error. The content
		/// of message is intended to be understood by humans. The caller of this constructor
		/// is required to ensure that this string has been localized for the current system culture.</param>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode"), DebuggerStepThrough]
		public static void NotNull<T>([ValidatedNotNull] T value, string message) where T : class
		{
			if (value == null) throw new NullReferenceException(message);
		}

		/// <summary>
		/// Throws <see cref="ArgumentNullException"/> if the given argument is null.
		/// </summary>
		/// <exception cref="ArgumentNullException"> if tested value is null.</exception>
		/// <param name="argumentValue">The value to test.</param>
		/// <param name="argumentName">The name of the parameter that caused the exception.</param>
		[DebuggerStepThrough]
		public static void ArgumentNotNull([ValidatedNotNull] object argumentValue, string argumentName)
		{
			if (argumentValue == null) throw new ArgumentNullException(argumentName);
		}

		/// <summary>
		/// Throws <see cref="ArgumentNullException"/> if the given argument is null.
		/// </summary>
		/// <exception cref="ArgumentNullException"> if tested value is null.</exception>
		/// <param name="argumentValue">The value to test.</param>
		/// <param name="argumentName">The name of the parameter that caused the exception.</param>
		/// <param name="errorMessage">A message that describes the error.</param>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode"), DebuggerStepThrough]
		public static void ArgumentNotNull([ValidatedNotNull] object argumentValue, string argumentName, string errorMessage)
		{
			if (argumentValue == null) throw new ArgumentNullException(argumentName, errorMessage);
		}

		/// <summary>
		/// Throws an exception if the tested string argument is null or an empty string.
		/// </summary>
		/// <exception cref="ArgumentNullException">Thrown if string value is null.</exception>
		/// <exception cref="ArgumentException">Thrown if the string is empty</exception>
		/// <param name="argumentValue">Argument value to check.</param>
		/// <param name="argumentName">Name of argument being checked.</param>
		[DebuggerStepThrough]
		public static void ArgumentNotNullOrEmpty([ValidatedNotNull] string argumentValue, string argumentName)
		{
			if (argumentValue == null) throw new ArgumentNullException(argumentName);
			if (argumentValue.Length == 0) throw new ArgumentException("The provided string argument must not be empty.", argumentName);
		}

		/// <summary>
		/// Throws an exception if the tested string argument is null or an empty string or whitespaces.
		/// </summary>
		/// <exception cref="ArgumentNullException">Thrown if string value is null.</exception>
		/// <exception cref="ArgumentException">Thrown if the string is empty</exception>
		/// <exception cref="ArgumentException">Thrown if the string is whitespace</exception>
		/// <param name="argumentValue">Argument value to check.</param>
		/// <param name="argumentName">Name of argument being checked.</param>
		[DebuggerStepThrough]
		public static void ArgumentNotNullOrEmptyOrWhitespace([ValidatedNotNull] string argumentValue, string argumentName)
		{
			if (argumentValue == null) throw new ArgumentNullException(argumentName);
			if (argumentValue.Length == 0) throw new ArgumentException("The provided string argument must not be empty.", argumentName);
			if (string.IsNullOrWhiteSpace(argumentValue)) throw new ArgumentException("The provided string argument must not be whitespaces.", argumentName);
		}

		/// <summary>
		/// Throws the ArgumentException if requested.
		/// </summary>
		/// <param name="throwIf">true to throw the execption; otherwise false.</param>
		/// <param name="argumentName">The Name of argument being checked.</param>
		/// <param name="message">The message related to the exception.</param>
		[DebuggerStepThrough]
		public static void ArgumentThrowIf(bool throwIf, string argumentName, string message)
		{
			if (throwIf)
				throw new ArgumentException(argumentName, message);
		}

		/// <summary>
		/// Verifies that an argument instance is assignable from the provided type (meaning
		/// interfaces are implemented, or classes exist in the base class hierarchy, or instance can be 
		/// assigned through a runtime wrapper, as is the case for COM Objects).
		/// </summary>
		/// <param name="assignmentTargetType">The argument type that will be assigned to.</param>
		/// <param name="assignmentInstance">The instance that will be assigned.</param>
		/// <param name="argumentName">Argument name.</param>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode"), DebuggerStepThrough]
		public static void InstanceIsAssignable(Type assignmentTargetType, object assignmentInstance, string argumentName)
		{
			if (assignmentInstance == null) throw new ArgumentNullException("assignmentInstance");

			Type assignmentType = assignmentInstance.GetType();

			Guard.TypeIsAssignable(assignmentTargetType, assignmentType, argumentName);
		}

		/// <summary>
		/// Verifies that an argument type is assignable from the provided type (meaning
		/// interfaces are implemented, or classes exist in the base class hierarchy).
		/// </summary>
		/// <param name="assignmentTargetType">The argument type that will be assigned to.</param>
		/// <param name="assignmentValueType">The type of the value being assigned.</param>
		/// <param name="argumentName">Argument name.</param>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode"), DebuggerStepThrough]
		public static void TypeIsAssignable(Type assignmentTargetType, Type assignmentValueType, string argumentName)
		{
			if (assignmentTargetType == null) throw new ArgumentNullException("assignmentTargetType");
			if (assignmentValueType == null) throw new ArgumentNullException("assignmentValueType");

			if (!assignmentTargetType.IsAssignableFrom(assignmentValueType))
			{
				throw new ArgumentException(string.Format(
					CultureInfo.CurrentCulture,
					"The type {1} cannot be assigned to variables of type {0}.",
					assignmentTargetType,
					assignmentValueType),
					argumentName);
			}
		}
	}
}